package com.y.report.hepler;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.*;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

@Slf4j
public class JdbcDriverLoaderHepler {


    /**
     * 从远端在家jdbc driver
     *
     * @param jarUrl          url全路径
     * @param driverClassName jdbc driver名称
     * @throws Exception
     */
    public static void loadRemoteJdbcDriverJar(String jarUrl, String driverClassName) throws Exception {
        String jarPath = jarUrl;
        if (jarUrl.startsWith("http")) {
            jarPath = downloadJdbcDriverToTempFile(jarUrl).getAbsolutePath();
        }
        loadAndRegisterJdbcDriver(jarPath, driverClassName);
    }

    private static File downloadJdbcDriverToTempFile(String jdbcDriverUrl) throws Exception {
        URL url = new URL(jdbcDriverUrl);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        int responseCode = httpConn.getResponseCode();

        if (responseCode == HttpURLConnection.HTTP_OK) {
            String fileName = url.getFile().substring(url.getFile().lastIndexOf("/") + 1);
            File outputFile = new File(System.getProperty("java.io.tmpdir"), fileName);
            if (outputFile.exists()) {
                outputFile.delete();
            }

            try (BufferedInputStream in = new BufferedInputStream(httpConn.getInputStream());
                 FileOutputStream out = new FileOutputStream(outputFile)) {

                byte[] dataBuffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = in.read(dataBuffer)) != -1) {
                    out.write(dataBuffer, 0, bytesRead);
                }
            }
            log.info("JDBC driver download successfully, url:{}, outputFile:{}", jdbcDriverUrl, outputFile);
            return outputFile;
        } else {
            throw new IOException("GET request not worked");
        }
    }

    private static void loadAndRegisterJdbcDriver(String jarFilePath, String driverClassName) throws Exception {
        URLClassLoader classLoader = loadJarByUrl(new File(jarFilePath).toURI().toURL(), null);

        //尝试SPI自动注册
        Class<?> driverClass = Class.forName(driverClassName, true, classLoader);

        //是否已注册
        boolean isAutoLoad = false;
        for (Enumeration<Driver> drivers = DriverManager.getDrivers(); drivers.hasMoreElements(); ) {
            Driver existsDriver = drivers.nextElement();
            //已通过spi注册
            if (existsDriver.getClass().equals(driverClass)) {
                isAutoLoad = true;
                break;
            }
            //已通过手动注册
            if (existsDriver instanceof DriverShim && ((DriverShim) existsDriver).driver.getClass().getName().equals(driverClass.getName())) {
                isAutoLoad = true;
                break;
            }
        }

        //手动注册
        if (!isAutoLoad) {
            Driver driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
            //DriverManager.registerDriver(driver);
            DriverManager.registerDriver(new DriverShim(driver));

            Set<Driver> existsDriverInfoSet = new LinkedHashSet<>();
            for (Enumeration<Driver> drivers = DriverManager.getDrivers(); drivers.hasMoreElements(); existsDriverInfoSet.add(drivers.nextElement())) {
            }
            log.info("[{} -> {}]JDBC driver loaded and registered successfully, exists loaded drivers:[{}]", driverClassName, jarFilePath, existsDriverInfoSet
                    .stream()
                    .map(x -> String.format("%s -> %s", x, x.getClass().getClassLoader()))
                    .collect(Collectors.joining("\t\n", "\n", "\n"))
            );
        }
    }


    public static URLClassLoader loadJarByUrl(URL url, URLClassLoader urlClassLoader) {
        try {
            if (urlClassLoader == null) {
                urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            }
            Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            addUrlMethod.setAccessible(true);
            addUrlMethod.invoke(urlClassLoader, url);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        return urlClassLoader;
    }

    // 禁止实例化工具类
    private JdbcDriverLoaderHepler() {
        throw new IllegalStateException("Utility class");
    }

    private static class DriverShim implements Driver {
        private Driver driver;

        DriverShim(Driver d) {
            this.driver = d;
        }


        @Override
        public Connection connect(String url, Properties info) throws SQLException {
            return this.driver.connect(url, info);
        }

        @Override
        public boolean acceptsURL(String url) throws SQLException {
            return this.driver.acceptsURL(url);
        }

        @Override
        public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
            return this.driver.getPropertyInfo(url, info);
        }

        @Override
        public int getMajorVersion() {
            return this.driver.getMajorVersion();
        }

        @Override
        public int getMinorVersion() {
            return this.driver.getMinorVersion();
        }

        @Override
        public boolean jdbcCompliant() {
            return this.driver.jdbcCompliant();
        }

        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return this.driver.getParentLogger();
        }
    }

}